package ro.myApp.security.service.user;

import java.util.LinkedList;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;

import ro.myApp.commons.dto.BaseDtoMessage;
import ro.myApp.commons.service.exceptions.ErrorKeys;
import ro.myApp.commons.service.exceptions.ViewException;
import ro.myApp.esb.service.MessageBasedService;
import ro.myApp.esb.service.MessageBasedServiceImpl;
import ro.myApp.security.dto.UserActivationMessage;
import ro.myApp.security.dto.UserChangePasswordMessage;
import ro.myApp.security.dto.UserCheckReminderCodeMessage;
import ro.myApp.security.dto.UserCreateMessage;
import ro.myApp.security.dto.UserFindAllFiltered;
import ro.myApp.security.dto.UserFindByUsernameMessage;
import ro.myApp.security.dto.UserForgotPasswordMessage;
import ro.myApp.security.dto.UserForgotUsernameMessage;
import ro.myApp.security.dto.UserRegistrationMessage;
import ro.myApp.security.dto.UserSaveMessage;
import ro.myApp.security.entities.User;
import ro.myApp.security.service.Encryption;

@Local(value=MessageBasedService.class)
@Remote(value=MessageBasedService.class)
@Stateless
public class UserRegistrationServiceImpl extends MessageBasedServiceImpl{
	
	@Resource
	protected SessionContext ctx;

	@EJB(mappedName="UserActionServiceImpl/local")
	private UserActionService userActionService; 

	@EJB(mappedName="UserAccessServiceImpl/local")
	private UserAccessService userAccessService; 
	
	@Override
	protected Object execute(BaseDtoMessage dto) throws Throwable {
		if (dto instanceof UserRegistrationMessage)
			return register(((UserRegistrationMessage) dto).getUser());
		else if (dto instanceof UserCreateMessage)
			return create(dto.getUserName());
		else if (dto instanceof UserFindAllFiltered)
			return filterUsers((UserFindAllFiltered) dto);
		else if (dto instanceof UserActivationMessage)
			return activateUser((UserActivationMessage) dto);
		else if (dto instanceof UserForgotPasswordMessage)
			return forgotPassword((UserForgotPasswordMessage) dto);
		else if (dto instanceof UserForgotUsernameMessage)
			return forgotUsername((UserForgotUsernameMessage) dto);
		else if (dto instanceof UserChangePasswordMessage)
			return userChangePassword((UserChangePasswordMessage) dto);
		else if (dto instanceof UserCheckReminderCodeMessage)
			return userCheckReminderCode((UserCheckReminderCodeMessage) dto);
		else if (dto instanceof UserFindByUsernameMessage)
			return findByProperty("username", ((UserFindByUsernameMessage) dto).getUsername());
		else if (dto instanceof UserSaveMessage)
			return saveUser ((UserSaveMessage)dto);
		throw new IllegalArgumentException(
				"This message type is currently not supported");
	}
	
	private Object saveUser(UserSaveMessage dto) {
		return userActionService.updateEntity(dto.getUser());
	}

	private User userChangePassword (UserChangePasswordMessage dto) throws Exception{
		User user = dto.getUser();
		user.setPassword(Encryption.encrypt(user.getPassword()));
		return userActionService.updateEntity(user);
	}
	
	private User userCheckReminderCode (UserCheckReminderCodeMessage dto) throws Exception{
		User user = null;
		try{
			System.out.println(ctx.getCallerPrincipal().getName());
			user = userAccessService.findUniqueFiltered(
					Restrictions.eq("username", dto.getUsername()),
					Restrictions.eq("active", Boolean.TRUE));
		}catch (NoResultException e1) {
			throw new ViewException(ErrorKeys.WRONG_ACTIVATION_USERNAME);
		} catch (NonUniqueResultException e) {
			throw new ViewException(ErrorKeys.WRONG_ACTIVATION_USERNAME);
		}
		//only check the code if user is not logged in
		if (!user.getUsername().equals(dto.getUserName()) && 
				!Encryption.encrypt(user.getId().toString()).equals(dto.getActivationCode()))
			 throw new ViewException(ErrorKeys.WRONG_ACTIVATION_CODE);
		
		return user;
	}
	
	private User forgotPassword (UserForgotPasswordMessage dto){
		User user = findByProperty("username", dto.getUsername());
		return user;
	}

	private User forgotUsername (UserForgotUsernameMessage dto){
		User user = findByProperty("emailAddress", dto.getEmail());
		return user;
	}
	
	private Boolean activateUser(UserActivationMessage dto) throws Exception {
		User user = null;
		try{
			user = findByProperty("username", dto.getUsername());
		}catch (NoResultException e1) {
			throw new ViewException(ErrorKeys.WRONG_ACTIVATION_USERNAME);
		} catch (NonUniqueResultException e) {
			throw new ViewException(ErrorKeys.WRONG_ACTIVATION_USERNAME);
		} 
		 //check for the proper activation code
		 if (!Encryption.encrypt(user.getId().toString()).equals(dto.getActivationCode()))
			 throw new ViewException(ErrorKeys.WRONG_ACTIVATION_CODE);
		 
		 //all ok
		 if (!user.isActive()){
			 user.setActive(true);
			 userActionService.updateEntity(user);
		 }
		 return Boolean.TRUE;
	}

	private List<User> filterUsers(UserFindAllFiltered dto) {
		List<Criterion> crit = new LinkedList<Criterion>();
		if (null != dto.getActive())
			crit.add(Restrictions.eq("active", dto.getActive()));
		if (null != dto.getUsername())
			crit.add(Restrictions.eq("username", dto.getUsername()));
		if (null != dto.getEmail())
			crit.add(Restrictions.eq("emailAddress", dto.getEmail()));
		if (null!=dto.getRole())
			crit.add(Restrictions.eq("role", dto.getRole()));
		if (dto.isWithInterests())
			crit.add(Restrictions.isNotNull("interests"));
		crit.add(Restrictions.eq("deleted", Boolean.FALSE));
		return userAccessService.findFiltered(crit.toArray(new Criterion[]{}));
	}

	public User register(User user) throws Exception {
		// check for validity unique username and email - if fail return false
		int countByUsername = userAccessService.countFiltered(
				Restrictions.eq("username", user.getUsername()));
		if (countByUsername>0){
			throw new ViewException(ErrorKeys.DUPLICATE_USERNAME);
		}
		
		int countByEmail = userAccessService.countFiltered( 
				Restrictions.eq("emailAddress", user.getEmailAddress()));
		if (countByEmail>0){
			throw new ViewException(ErrorKeys.DUPLICATE_EMAIL);
		}
		
		// finally save user in db
		user.setCreatedByUsername(user.getUsername());
		user.setPassword(Encryption.encrypt(user.getPassword()));
		User registered = this.userActionService.updateEntity(user);
	
		return registered;
	}

	public User create(String user) {
		return userActionService.createEntity(User.class, user);
	}

	private User findByProperty(String property, Object value) {
		return this.userAccessService.findUniqueFiltered(Restrictions.eq(property, value));
	}
}